package org.moonlight.jvm.rtda.exclusive;

import lombok.EqualsAndHashCode;

/**
 * 运行时数据区 - 线程独占区 - 线程 - 虚拟机栈
 *
 * 线程独占区主要是用于模拟CPU，辅助执行Java字节码。线程独占区中包含有 虚拟机栈，本地方法栈，程序计数器。
 *   1. 虚拟机栈: 描述的是Java方法执行的内存模型，生命周期和线程相同。可能产生 SOE、OOM
 *              每个方法执行时都会创建一个栈帧，栈帧中包含：操作数栈、局部变量表、动态连接、方法出口、指向常量池的指针等等方法执行所需要的信息
 *                 a.首先给线程分配一个栈空间，每个线程都有自己的线程栈
 *                 b.在线程执行方法的时候，会把当前方法的相关数据(比如参数/局部变量数/方法栈深),封装成栈帧压入线程栈中
 *                   如果在执行时发现方法里又套了其他方法,那么又会把被调用的方法封装成另一个栈帧,压到栈顶并执行,执行完
 *                   后弹出,如果有返回值就拿到它的返回值,返回给开始执行的那个方法(调用者)并继续执行,如果整个方法执行完毕,线程栈销毁,线程释放资源(如果没有使用线程池的话)
 *   2. 本地方法栈: 和虚拟机栈差不多，只不过虚拟机栈是给虚拟机执行Java方法服务的，本地方法栈是给native方法服务的。可能产生SOE、OOM。
 *   3. 程序计数器(PC): 记录当前线程所执行的字节码的行号. 条件分支、循环跳转、异常处理、线程恢复 都依赖于它. 没有OOM异常
 *                如果当前执行的方法是Java方法那么程序计数器中存储的就是当前正在执行的字节码的行号，否则，当前方法是本地方法，程序计数器中的值没有明确定义
 *
 * @author Moonlight
 * @createTime 2023/8/16 10:36
 **/
@EqualsAndHashCode
public class JvmStack {

    /** 虚拟机栈的最大栈深 **/
    private int maxSize;
    /** 虚拟机栈的当前栈深 **/
    private int size;
    /** 虚拟机栈的当前栈顶栈帧 **/
    private Frame top;

    public JvmStack(int maxSize) {
        this.maxSize = maxSize;
    }

    /**
     * 栈帧压栈
     * 如果 虚拟机栈的当前栈深大于虚拟机栈的最大栈深，直接抛出 StackOverflowError
     *
     * @param frame 栈帧 {@link Frame}
     * @createTime 11:34 2023/8/16
     * @author moonlight
     **/
    public void push(Frame frame) {
        if (this.size > this.maxSize) {
            throw new StackOverflowError();
        }
        if (this.top != null) {
            frame.setNext(this.top);
        }
        this.top = frame;
        this.size++;
    }

    public Frame pop() {
        if (this.top == null) {
            throw new RuntimeException(" jvm stack is empty ");
        }

        Frame top = this.top;
        this.top = top.getNext();
        top.setNext(null);
        this.size--;

        return top;
    }

    /**
     * 获取虚拟机栈栈顶的栈帧，但是不弹栈
     *
     * @return Frame 栈帧{@link Frame}
     * @createTime 11:37 2023/8/16
     * @author moonlight
     **/
    public Frame top() {
        if (this.top == null){
            throw new RuntimeException(" jvm stack is empty ");
        }
        return this.top;
    }

    public boolean isEmpty() {
        return this.top == null;
    }

    public void clear() {
        while (!this.isEmpty()) {
            this.pop();
        }
    }

    public Frame[] getFrames() {
        Frame[] frames = new Frame[this.size];
        int i = 0;
        for (Frame frame = this.top; frame != null; frame = frame.getNext()) {
            frames[i++] = frame;
        }
        return frames;
    }
}